package com.lu.sb210413.cameraDetection;

import com.lu.sb210413.utils.Md5Utils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;


public class RTSPClient extends Thread {

    private static final String VERSION = " RTSP/1.0\r\n";
    private static final String RTSP_OK = "RTSP/1.0 200 OK";
    private static final String RTSP_Unauthorized = "RTSP/1.0 401 Unauthorized";

    private final InetSocketAddress remoteAddress;

    private SocketChannel socketChannel;

    private final ByteBuffer sendBuf;

    private final ByteBuffer receiveBuf;

    private static final int BUFFER_SIZE = 8192;

    private Selector selector;

    private String address;

    private AtomicBoolean shutdown;

    private int seq = 1;

    private boolean isSended;

    private String username;

    private String password;

    public RTSPClient(InetSocketAddress remoteAddress, String address, String username, String password) {
        this.remoteAddress = remoteAddress;
        this.address = address;
        this.username = username;
        this.password = password;

        // 初始化缓冲区
        sendBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
        receiveBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
        if (selector == null) {
            // 创建新的Selector
            try {
                selector = Selector.open();
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }

        startup();
        shutdown = new AtomicBoolean(false);
        isSended = false;
    }

    private void startup() {
        try {
            // 打开通道
            socketChannel = SocketChannel.open();
            socketChannel.socket().setSoTimeout(30000);
            socketChannel.configureBlocking(false);
            if (socketChannel.connect(remoteAddress)) {
                System.out.println("开始建立连接:" + remoteAddress);
            }
            socketChannel.register(selector, SelectionKey.OP_CONNECT
                    | SelectionKey.OP_READ | SelectionKey.OP_WRITE, this);

        } catch (final IOException e1) {
            e1.printStackTrace();
        }
    }

    private void send(byte[] out) {
        if (out == null || out.length < 1) {
            return;
        }
        synchronized (sendBuf) {
            sendBuf.clear();
            sendBuf.put(out);
            sendBuf.flip();
        }

        // 发送出去
        try {
            write();
            isSended = true;
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    private void write() throws IOException {
        if (isConnected()) {
            try {
                socketChannel.write(sendBuf);
            } catch (final IOException e) {
            }
        } else {
            System.out.println("通道为空或者没有连接上！");
            // TODO 该相机不可用（数据库）
        }
    }

    private byte[] recieve() {
        if (isConnected()) {
            try {
                int len = 0;
                int readBytes = 0;

                synchronized (receiveBuf) {
                    receiveBuf.clear();
                    try {
                        while ((len = socketChannel.read(receiveBuf)) > 0) {
                            readBytes += len;
                        }
                    } finally {
                        receiveBuf.flip();
                    }
                    if (readBytes > 0) {
                        final byte[] tmp = new byte[readBytes];
                        receiveBuf.get(tmp);
                        return tmp;
                    } else {
                        System.out.println("接收到数据为空！");
                        return null;
                    }
                }
            } catch (final IOException e) {
                System.out.println("接收消息错误！");
            }
        } else {
            System.out.println("端口没有连接！");
        }
        return null;
    }

    private boolean isConnected() {
        return socketChannel != null && socketChannel.isConnected();
    }

    private void select() {
        int n = 0;
        try {
            if (selector == null) {
                return;
            }
            n = selector.select(1000);

        } catch (final Exception e) {
            e.printStackTrace();
        }

        // 如果select返回大于0，处理事件
        if (n > 0) {
            for (final Iterator<SelectionKey> i = selector.selectedKeys()
                    .iterator(); i.hasNext(); ) {
                // 得到下一个Key
                final SelectionKey sk = i.next();
                i.remove();
                // 检查其是否还有效
                if (!sk.isValid()) {
                    continue;
                }
                try {
                    if (sk.isConnectable()) {
                        connect(sk);
                    } else if (sk.isReadable()) {
                        read(sk);
                    } else {
                    }
                } catch (final Exception e) {
                    error(e);
                    sk.cancel();
                }
            }
        }
    }

    private void shutdown() {
        if (isConnected()) {
            try {
                socketChannel.close();
                System.out.println("端口关闭成功！");
            } catch (final IOException e) {
                System.out.println("端口关闭错误！");
            } finally {
                socketChannel = null;
            }
        } else {
            System.out.println("通道为空或者没有连接！");
            // TODO 该相机不可用（数据库）
        }
    }

    @Override
    public void run() {
        long millis = System.currentTimeMillis();
        // 启动主循环流程
        while (!shutdown.get()) {
            if (System.currentTimeMillis() - millis > 5000) {
                shutdown.set(true);
                // TODO 该相机不可用（数据库）
            }
            try {
                if (isConnected() && (!isSended)) {
                    doDescribe();
                }
                // do select
                select();
                try {
                    Thread.sleep(1000);
                } catch (final Exception e) {
                }
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
        shutdown();
    }

    private void connect(SelectionKey key) {
        if (isConnected()) {
            return;
        }
        // 完成SocketChannel的连接
        try {
            socketChannel.finishConnect();
            while (!socketChannel.isConnected()) {
                Thread.sleep(300);
                socketChannel.finishConnect();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
            shutdown.set(true);
            // TODO 该相机不可用（数据库）
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void error(Exception e) {
        e.printStackTrace();
    }

    private void read(SelectionKey key) throws IOException {
        // 接收消息
        final byte[] msg = recieve();
        if (msg != null) {
            handle(msg);
        } else {
            key.cancel();
        }
    }

    private void handle(byte[] msg) {
        String tmp = new String(msg);
        if (tmp.startsWith(RTSP_OK)) {
            isSended = false;
            shutdown.set(true);
            System.out.println("返回正确：\n" + tmp);
            // TODO 该相机可用（数据库）
        } else if(tmp.startsWith(RTSP_Unauthorized)) {
            System.out.println("返回验证：\n" + tmp);
            String[] digest = tmp.split("WWW-Authenticate: Digest ")[1].split("\n")[0].split(", ");
            doDigest(digest);
        }
        else {
            shutdown.set(true);
            System.out.println("返回错误：\n" + tmp);
            // TODO 该相机不可用（数据库）
        }

    }

    private void doDescribe() {
        StringBuilder sb = new StringBuilder();
        sb.append("DESCRIBE ");
        sb.append(this.address);
        sb.append(VERSION);
        sb.append("CSeq: ");
        sb.append(seq);
        sb.append("\r\n");
        sb.append("\r\n");
        System.out.println(sb.toString());
        send(sb.toString().getBytes());
    }

    private void doDigest(String[] digest) {
        String realm = "";
        String nonce = "";
        for (String d : digest) {
            if (d.contains("realm")) {
                realm = d.replace("\"", "").split("=")[1];
            }
            if (d.contains("nonce")) {
                nonce = d.replace("\"", "").split("=")[1];
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append("DESCRIBE ");
        sb.append(this.address);
        sb.append(VERSION);
        sb.append("CSeq: ");
        sb.append(++seq);
        sb.append("\r\n");
        sb.append("Authorization: Digest ").append("username=\"").append("admin").append("\", ")
                .append("realm=\"").append(realm).append("\", ")
                .append("nonce=\"").append(nonce).append("\", ")
                .append("uri=\"").append(this.address).append("\", ")
                .append("response=\"").append(
                        Md5Utils.hash(
                                (Md5Utils.hash(this.username + ":" + realm + ":" + this.password) + ":" + nonce + ":" + Md5Utils.hash("DESCRIBE:" + this.address))
                        )
                        ).append("\"");
        sb.append("\r\n");
        sb.append("\r\n");
        System.out.println(sb.toString());
        send(sb.toString().getBytes());
    }

}
